/*
 * Copyright (C) Francisco Morero Peyrona. All rights reserved.
 *
 * This software is published under the terms of Open Source
 * License version 1.1, a copy of which has been included with this
 * distribution in the License.txt file.
 */

package jdbcmanager.swing;

import java.sql.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import jdbcmanager.app.*;
import jdbcmanager.diagram.*;
import jdbcmanager.procedure.*;
import jdbcmanager.udt.*;
import jdbcmanager.table.*;
import jdbcmanager.tree.*;

import jdbcmanager.dbtools.*;
import jdbcmanager.dbtools.datatypes.*;
import jdbcmanager.sqlverboser.*;
import jdbcmanager.swing.desktop.*;
import jdbcmanager.swing.help.*;

/**
 * Main Frame.
 * <p>
 * This class also has those public methods that can be used from any place in the application.
 * <p>
 * The only one instance of it is available from any point via: <code>App.getFrameMain()</code>
 * 
 * @author Francisco Morero Peyrona
 */

public final class JFrameMain extends JFrame
{
   private JxDesktopPane desktop;
   private DbTree        tree;
   private JSqlVerboser  sqlverbo;
   private JHelp         help;

   private JSplitPane splitLeftRight;
   private JSplitPane splitTreeHelp;
   private JSplitPane splitDesktopVerboser;
   
   //-------------------------------------------------------------------------//

   /**
    * Class constructor.
    */
   public JFrameMain()
   {
      super( App.sAPP_TITLE );

      setIconImage( (new ImageIcon( getClass().getResource( "images/app_icon.gif" ) )).getImage() );
      init();

      addComponentListener( new ComponentAdapter()
      {
         public void componentResized( ComponentEvent evt )
         {
            onWndResized();
         } 
      } );

      addWindowListener( new WindowAdapter()
      {
         public void windowClosing( WindowEvent evt )
         {
            onWindowClosing();   // Saves frame current status
         }
      } );
   }

   /**
    * Perform house-keeping before window is closed.
    * <p>
    * Should be called only from App.stop() method. 
    */
   public void close()
   {
      App.props().setWndX( getX() );
      App.props().setWndY( getY() );
      App.props().setWndWidth( getWidth() );
      App.props().setWndHeight( getHeight() );

      App.props().setSplitTreeHelp(     this.splitTreeHelp.getDividerLocation() );
      App.props().setSplitDesktopVerbo( this.splitDesktopVerboser.getDividerLocation() );
      App.props().setSplitLeftRight(    this.splitLeftRight.getDividerLocation() );
   }

   /**
    * House keeping after a DBMS connection is stablished.
    */
   public void connectionStablished()
   {      
      String sTitle  = getTitle();
      String sServer = DbUtil.getAgentDBMS().getAgentMetaData().getDatabaseProductName(); 
      
      if( sServer == null || sServer.length() == 0 )
         sServer = DbUtil.getAgentDBMS().getDriverFamiliarName();

      this.desktop.closeAll();
      this.sqlverbo.clear();

      // Invokes tree reload
      this.tree.connectionStablished( DbUtil.getURL() );

      // Removes title information about current DBMS if any
      if( sTitle.indexOf( '[' ) > -1 ) // Remove 3 extra spaces
         sTitle = sTitle.substring( 0, sTitle.indexOf( '[' ) - 1 ).trim();

      setTitle( sTitle +"   [DBMS: "+ sServer +']' );
      this.sqlverbo.updateCatalogTerm();
   }

   /**
    * These are all commands executed via <code>DbUtil.executeSQL( ... )</code> method.
    * 
    * @param sCatalog The catalog
    * @param sSQL Executed command
    * @param nTime Time needed to be executed (time starts when the command is sent to the DMBS 
    *        and ends when the control is returned back to the application)
    */
   public void sqlVerbose( String sCatalog, String sSQL, long nTime )
   {
      this.sqlverbo.add( sCatalog, sSQL, nTime );
   }
   
   /**
    * Retrieves an array of strings were each element is a serialized internal frame.
    *  
    * @return An array of strings were each element is a serialized internal frame
    */
   public String[] serializeSession()
   {
      JInternalFrame[] aIF = this.desktop.getAllFrames();
      Vector           vec = new Vector( aIF.length ); 
      
      for( int n = 0; n < aIF.length; n++ )
      {
         if( aIF[n] instanceof JDBCManagerInternalFrame )
         {
            String serial = ((JDBCManagerInternalFrame) aIF[n]).serialize();
                   serial = aIF[n].getClass().getName() +';'+ serial; 
            vec.add( serial );
         }
      }
      
      String[] asRet = new String[ vec.size() ];
      vec.copyInto( asRet ); 
      
      return asRet;
   }
   
   /**
    * 
    * @param asSerial
    */
   public void deserializeSession( String[] asSerial )
   {
      Cursor old = getCursor(); 
      setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
      
      for( int n = 0; n < asSerial.length; n++ )
      {
         StringTokenizer          st      = new StringTokenizer( asSerial[n], ";", false );
         String                   sClass  = st.nextToken();
         JDBCManagerInternalFrame frm     = null;
   
         if(      sClass.endsWith( "JSQLInternalFrame"       ) ) frm = new JSQLInternalFrame();   
         else if( sClass.endsWith( "JDiagramInternalFrame"   ) ) frm = new JDiagramInternalFrame();
         else if( sClass.endsWith( "JProcedureInternalFrame" ) ) frm = new JProcedureInternalFrame();
         else if( sClass.endsWith( "JUDTInternalFrame"       ) ) frm = new JUDTInternalFrame();
   
         frm.deserialize( st );
         frm.setVisible( true );
         this.desktop.add( frm );
         this.desktop.setActiveFrame( frm );
      }
      
      setCursor( old ); 
   }

   /**
    * Tell to the desktop about to show its popup menu.
    * 
    * @param invoker  Whom
    * @param x        x displacement from invloker
    * @param y        y displacement from invloker
    */
   public void showDesktopPopup( JComponent invoker, int x, int y )
   {
      this.desktop.showPopup( invoker, x, y );
   }

   /**
    * Changes HTML help pane content (help tab under DBMS tree)
    * 
    * @param sContext New context descriptor
    */
   public void setHelpContext( String sContext )
   {
      this.help.setContext( sContext );
   }

   /**
    *
    * @param  sCatalog
    * @param  sSQL
    * @return A reference to created internal frame
    */
   public JDBCManagerInternalFrame openSqlQuery( DbEntityDescriptor ded )
   {
      Connection conn = DbUtil.getConnection( ded.getCatalog() );  // Used ony to check that user knows User and Password
      JDBCManagerInternalFrame frm  = null;

      if( conn != null )
      {  // This is a trick: using ded for Query I.F., all I.F. use ded and serialize + deserialize is much simpler    
         frm = new JSQLInternalFrame( ded );
         frm.setVisible( true );

         this.desktop.add( frm );
         this.desktop.setActiveFrame( frm );
      }

      return frm;
   }

   /**
    * Creates an InternalFrame which content is the component returned by "diagram.DbDiagram" class.
    * The returned reference is stored in Tree Node to do frm.toFront() next time the node is clicked.
    * <p>
    * @param  ded
    * @return A reference to created internal frame
    */
   public JDBCManagerInternalFrame openDiagram( DbEntityDescriptor ded )
   {
      Connection conn = DbUtil.getConnection( ded.getCatalog() );  // Used ony to check that user knows User and Password
      JDBCManagerInternalFrame frm  = null;

      if( conn != null )
      {
         frm = new JDiagramInternalFrame( ded );
         frm.setVisible( true );

         this.desktop.add( frm );
         this.desktop.setActiveFrame( frm );
      }

      return frm;
   }

   /**
    * Creates an InternalFrame which content is the component returned by "procedure.DbProcedure" class.
    * The returned reference is stored in Tree Node to do frm.toFront() next time the node is clicked.
    *
    * @param  ded
    * @return A reference to created internal frame
    */
   public JDBCManagerInternalFrame openProcedure( DbEntityDescriptor ded )
   {
      Connection conn = DbUtil.getConnection( ded.getCatalog() );  // Used ony to check that user knows User and Password
      JDBCManagerInternalFrame frm  = null;

      if( conn != null )
      {
         frm = new JProcedureInternalFrame( ded );
         frm.setVisible( true );

         this.desktop.add( frm );
         this.desktop.setActiveFrame( frm );
      }

      return frm;
   }
   
   /**
    * Creates an InternalFrame which content is the component returned by "udt.DbUDT" class.
    * The returned reference is stored in Tree Node to do frm.toFront() next time the node is clicked.
    * 
    * @param  ded
    * @return A reference to created internal frame
    */
   public JDBCManagerInternalFrame openUDT( DbEntityDescriptor ded )
   {
      Connection conn = DbUtil.getConnection( ded.getCatalog() );  // Used ony to check that user knows User and Password
      JDBCManagerInternalFrame frm  = null;

      if( conn != null )
      {
         frm = new JUDTInternalFrame( ded );
         frm.setVisible( true );

         this.desktop.add( frm );
         this.desktop.setActiveFrame( frm );
      }

      return frm;
   }

   private void init()
   {
      this.tree     = new DbTree();
      this.sqlverbo = new JSqlVerboser();
      this.help     = new JHelp();
      this.desktop  = new JxDesktopPane();
      this.desktop.setBackground( new Color( 52, 93, 140 ) );

      // Reads last position and size from props file.
      // If it is the first time: calculates frmMain size and centers in screen.
      // It is needed to set size here because following lines use getHeight() & getWidth()
      Dimension scr  = Toolkit.getDefaultToolkit().getScreenSize();
      int nWndWidth  = (int) (scr.getWidth()  - (scr.getWidth() / 10));
      int nWndHeight = (int) (scr.getHeight() - (scr.getHeight() / 10));
      int nWndX      = (int) (scr.getWidth()  - nWndWidth) / 2;
      int nWndY      = (int) (scr.getHeight() - nWndHeight) / 2;

      setBounds( App.props().getWndX( nWndX ), 
                 App.props().getWndY( nWndY ),
                 App.props().getWndWidth( nWndWidth ), 
                 App.props().getWndHeight( nWndHeight ) );

      // Contains JTree (north) and JHelp (south)
      this.splitTreeHelp = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
      this.splitTreeHelp.setOneTouchExpandable( true );
      this.splitTreeHelp.setDividerLocation( App.props().getSplitTreeHelp( getHeight() - 360 ) );
      this.splitTreeHelp.setTopComponent( this.tree );
      this.splitTreeHelp.setBottomComponent( this.help );

      // Contains JDesktopPane (north) and JSqlVerboser (south)
      this.splitDesktopVerboser = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
      this.splitDesktopVerboser.setOneTouchExpandable( true );
      this.splitDesktopVerboser.setDividerLocation( App.props().getSplitDesktopVerbo( getHeight() - 160 ) );
      this.splitDesktopVerboser.setTopComponent( new JScrollPane( this.desktop ) );
      this.splitDesktopVerboser.setBottomComponent( this.sqlverbo );
      this.splitDesktopVerboser.addPropertyChangeListener( JSplitPane.DIVIDER_LOCATION_PROPERTY, new java.beans.PropertyChangeListener()
      {
         public void propertyChange( java.beans.PropertyChangeEvent evt )
         {
               JFrameMain.this.desktop.checkSize();
         } 
      } );

      // Both Spliters (left and Right)
      this.splitLeftRight = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
      this.splitLeftRight.setOneTouchExpandable( true );
      this.splitLeftRight.setDividerLocation( App.props().getSplitLeftRight( 280 ) );
      this.splitLeftRight.setLeftComponent(  splitTreeHelp  );
      this.splitLeftRight.setRightComponent( splitDesktopVerboser );
      this.splitLeftRight.addPropertyChangeListener( new java.beans.PropertyChangeListener()
      {
         public void propertyChange( java.beans.PropertyChangeEvent evt )
         {
            if( evt.getPropertyName().equals( "dividerLocation" ) )
               JFrameMain.this.desktop.checkSize();
         } 
      } );

      getContentPane().add( splitLeftRight, BorderLayout.CENTER );
      setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE );
   }

   //-------------------------------------------------------------------------//

   private void onWndResized()
   {
      this.desktop.checkSize();
      this.splitDesktopVerboser.setDividerLocation( this.splitDesktopVerboser.getMaximumDividerLocation() );
   }

   private void onWindowClosing()
   {
      int nOption = JOptionPane.OK_OPTION;
      
      if( App.props().getAskOnExit( true ) )
      {
         JPanel    pnl = new JPanel( new BorderLayout() );
         JLabel    lbl = new JLabel( "Are you sure you want to exit "+ App.sAPP_NAME +"?" );
         JCheckBox chk = new JCheckBox( "Do not ask in the future" );
                   chk.setBorder( BorderFactory.createEmptyBorder( 28,0,18,0 ) );
         
         pnl.add( lbl, BorderLayout.CENTER );
         pnl.add( chk, BorderLayout.SOUTH  );
         pnl.setBorder( BorderFactory.createEmptyBorder( 10,10,10,10 ) );
         
         nOption = JOptionPane.showConfirmDialog( this, pnl, "Confirm exit",
                                                  JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE );                                         
         if( chk.isSelected() )
            App.props().setAskOnExit( false );
      }
                                                   
      if( nOption == JOptionPane.OK_OPTION )
         App.stop();  // Calls this.close() --> that saves positions
   }

   //-------------------------------------------------------------------------//
   // JDBCManagerInternalFrame is the parent class for: JSqlQueryInternalFrame, 
   //     JDiagramInternalFrame, JProcedureInternalFrame and JUDTInternalFrame
   //-------------------------------------------------------------------------//
   public abstract class JDBCManagerInternalFrame extends JxInternalFrame
   {
      protected DbEntityDescriptor ded;
      private   String             sHelpContext;
      
      JDBCManagerInternalFrame( String sHelpContext )
      {
         super();
         
         setLocation( 7,7 );
         setDefaultCloseOperation( DISPOSE_ON_CLOSE );
         this.sHelpContext = sHelpContext;
         
         addInternalFrameListener( new InternalFrameAdapter()
         {
            public void internalFrameActivated( InternalFrameEvent evt )
            {
               JFrameMain.this.setHelpContext( JDBCManagerInternalFrame.this.sHelpContext );
            }
         } );
      }
      
      abstract void setDbEntityDescriptor( DbEntityDescriptor ded );
      
      public void rename( String sNewName )
      {
         this.ded.setName( sNewName );
      }
      
      String serialize()
      {
         StringBuffer sb = new StringBuffer( 512 );
                      sb.append( this.ded.getCatalog() ).append( ';' );
                      sb.append( this.ded.getSchema()  ).append( ';' );
                      sb.append( this.ded.getName()    ).append( ';' );
                      sb.append( this.ded.getType()    ).append( ';' );
                      sb.append( getX()      ).append( ';' );
                      sb.append( getY()      ).append( ';' );
                      sb.append( getWidth()  ).append( ';' );
                      sb.append( getHeight() );
                      
         return sb.toString();
      }
      
      void deserialize( StringTokenizer st )
      {
         DbEntityDescriptor ded = new DbEntityDescriptor( st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken() );
         
         int nX = Integer.parseInt( st.nextToken() );
         int nY = Integer.parseInt( st.nextToken() );
         int nW = Integer.parseInt( st.nextToken() );
         int nH = Integer.parseInt( st.nextToken() );
         
         setDbEntityDescriptor( ded );
         setBounds( nX, nY, nW, nH );
      }
   }
   
   //-------------------------------------------------------------------------//
   // INNER CLASS: JSqlQueryInternalFrame
   //-------------------------------------------------------------------------//
   private final class JSQLInternalFrame extends JDBCManagerInternalFrame
   {
      private DbTable table;
      
      JSQLInternalFrame()
      {
         this( null );
      }
      
      JSQLInternalFrame( DbEntityDescriptor ded )
      {
         super( JHelp.TABLE );
         
         setFrameIcon( new ImageIcon( DbTree.class.getResource( "images/tree_table.gif" ) ) );
         
         if( ded != null )
            setDbEntityDescriptor( ded );
         else
            pack();
      }

      void setDbEntityDescriptor( DbEntityDescriptor ded )
      {
         this.ded = ded;
         
         setTitle( " SQL Manager on '"+ ded.getCatalog() +'\'' );
         
         Connection conn = DbUtil.getConnection( ded.getCatalog() );
         String     sSQL = "SELECT * FROM "+ ded.getQualifiedName();
         
         this.table = new DbTable( conn );
         this.table.executeSqlCommand( sSQL );  
         
         getContentPane().add( table );
         pack();
      }
      
      // This class does not need to redefine rename(...)
   }

   //-------------------------------------------------------------------------//
   // INNER CLASS: JDiagramInternalFrame
   //-------------------------------------------------------------------------//
   private final class JDiagramInternalFrame extends JDBCManagerInternalFrame
   {
      private DbDiagram diagram;
      
      JDiagramInternalFrame()
      {
         this( null );
      }
      
      JDiagramInternalFrame( DbEntityDescriptor ded )
      {
         super( JHelp.DIAGRAM );
         
         addInternalFrameListener( new InternalFrameAdapter()
         {
            public void internalFrameClosing( InternalFrameEvent evt )
            {
               JDiagramInternalFrame.this.diagram.close();
            }
         } );
         
         Rectangle parent = JFrameMain.this.desktop.getVisibleRect();
         
         setFrameIcon( new ImageIcon( DbTree.class.getResource( "images/tree_diagram.gif" ) ) );
         setSize( parent.width - (parent.width / 10), parent.height - (parent.height / 10) );
         
         if( ded != null )
            setDbEntityDescriptor( ded );
      }
      
      void open( DbEntityDescriptor ded )
      {
          setDbEntityDescriptor( ded );
      }

      void setDbEntityDescriptor( DbEntityDescriptor ded )
      {
         this.diagram = new DbDiagram( ded );
         this.diagram.addDbChangeListener( JFrameMain.this.tree );   // The tree needs to know diagrams event to refresh its inf.
         JFrameMain.this.tree.addDbChangeListener( this.diagram  );  // The diagram needs to know tree event to refresh its inf.
         this.ded = ded;
         
         setTitle( " Diagram '"+  this.ded.getName() +"' in '"+ this.ded.getCatalog() +'\'' );
         getContentPane().add( this.diagram );
         this.diagram.open();
      }
      
      public void rename( String sNewName )
      {
         super.rename( sNewName );
         setTitle( " Diagram '"+  this.ded.getName() +"' in '"+ this.ded.getCatalog() +'\'' );
      }
   }

   //-------------------------------------------------------------------------//
   // INNER CLASS: JProcedureInternalFrame
   //-------------------------------------------------------------------------//
   private final class JProcedureInternalFrame extends JDBCManagerInternalFrame
   {
      JProcedureInternalFrame()
      {
         this( null );
      }
      
      JProcedureInternalFrame( DbEntityDescriptor ded )
      {
         super( JHelp.PROCEDURE );
         
         setFrameIcon( new ImageIcon( DbTree.class.getResource( "images/tree_procedure.gif" ) ) );
         
         if( ded != null )
            setDbEntityDescriptor( ded );
         else
            pack();
      }

      void setDbEntityDescriptor( DbEntityDescriptor ded )
      {
         this.ded = ded;
         
         setTitle( ' '+ DbUtil.getAgentDBMS().getAgentMetaData().getProcedureTerm() +" '"+ ded.getName() +"' in '"+ ded.getCatalog() +'\'' );
         getContentPane().add( new DbProcedure( DbUtil.getAgentDBMS().getAgentMetaData().getProcedure( ded ) ) );
         pack();
         setSize( Math.min( getWidth(), 470 ), Math.min( getHeight(), 420 ) );
      }
      
      public void rename( String sNewName )
      {
         super.rename( sNewName );
         setTitle( ' '+ DbUtil.getAgentDBMS().getAgentMetaData().getProcedureTerm() +" '"+ ded.getName() +"' in '"+ ded.getCatalog() +'\'' );
      }
   }
   
   //-------------------------------------------------------------------------//
   // INNER CLASS: JUDTInternalFrame
   //-------------------------------------------------------------------------//
   private final class JUDTInternalFrame extends JDBCManagerInternalFrame
   {
      JUDTInternalFrame()
      {
         this( null );   
      }
      
      JUDTInternalFrame( DbEntityDescriptor ded )
      {
         super( JHelp.UDT );
         
         setFrameIcon( new ImageIcon( DbTree.class.getResource( "images/tree_udt.gif" ) ) );
         
         if( ded != null )
            setDbEntityDescriptor( ded );
         else
            pack();
      }

      void setDbEntityDescriptor( DbEntityDescriptor ded )
      {
         this.ded = ded;
         
         setTitle( " User Defined Type '"+ ded.getName() +"' in '"+ ded.getCatalog() +'\'' );
         getContentPane().add( new DbUDT( DbUtil.getAgentDBMS().getAgentMetaData().getUDT( ded ) ) );   
         pack();
         setSize( Math.min( getWidth(), 400 ), Math.min( getHeight(), 350 ) );   // Set max size
      }
      
      public void rename( String sNewName )
      {
         super.rename( sNewName );
         setTitle( " User Defined Type '"+ ded.getName() +"' in '"+ ded.getCatalog() +'\'' );
      }
   }
}